package thesis.filesharing.dao.impl;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import javax.persistence.Query;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import org.springframework.stereotype.Repository;

import thesis.filesharing.common.exception.system.AppSystemException;
import thesis.filesharing.common.valueobjects.SearchStudentValueObject;
import thesis.filesharing.dao.StudentDAO;
import thesis.filesharing.dao.util.EMF;
import thesis.filesharing.model.Clazz;
import thesis.filesharing.model.Student;

import com.google.appengine.api.datastore.Key;

@Repository
public class StudentDAOImpl implements StudentDAO {
	
	@SuppressWarnings("unchecked")
	@Override
	public Student findStudentById(String studentId) {
		EntityManager em = null;
		try {
			em = EMF.get().createEntityManager();
			String jpaql = "SELECT s FROM Student s WHERE s.studentId = :studentId";
			Query query = em.createQuery(jpaql);
			query.setParameter("studentId", studentId);
			List<Student> students = (List<Student>) query.getResultList();
			if (students != null && !students.isEmpty()) {
				return students.get(0);
			}
			return null;
		} catch (Exception ex) {
			ex.printStackTrace();
			throw new AppSystemException(ex);
		} finally {
			em.close();
		}
	}

	@SuppressWarnings("unchecked")
	@Override
	public Student findStudentByEmail(String studentEmail) {
		EntityManager em = null;
		try {
			em = EMF.get().createEntityManager();
			String jpaql = "SELECT s FROM Student s WHERE s.studentEmail = :studentEmail";
			Query query = em.createQuery(jpaql);
			query.setParameter("studentEmail", studentEmail);
			List<Student> students = (List<Student>) query.getResultList();
			if (students != null && !students.isEmpty()) {
				return students.get(0);
			}
			return null;
		} catch (Exception ex) {
			ex.printStackTrace();
			throw new AppSystemException(ex);
		}
	}

	@Override
	public Student saveStudent(Student student) {
		EntityManager em = null;
		EntityTransaction transaction = null;
		
		try {
			em = EMF.get().createEntityManager();
			transaction = em.getTransaction();
			transaction.begin();
			
			// Persist student
			em.persist(student);
			em.flush();
			
			transaction.commit();
			return student;
		} catch (Exception ex) {
			if (transaction != null && transaction.isActive()) {
				transaction.rollback();
			}
			ex.printStackTrace();
			throw new AppSystemException(ex);
		}
	}

	@SuppressWarnings("unchecked")
	@Override
	public List<Student> listStudents(int page, int pageSize) {
		EntityManager em = null;
		try {
			em = EMF.get().createEntityManager();
			String jpaql = "SELECT s FROM Student s ORDER BY s.studentId";
			Query query = em.createQuery(jpaql);
			query.setFirstResult((page - 1) * pageSize);
			query.setMaxResults(pageSize);
			return (List<Student>) query.getResultList();
		} catch (Exception ex) {
			ex.printStackTrace();
			throw new AppSystemException(ex);
		}
	}

	@Override
	public int countStudents() {
		EntityManager em = null;
		try {
			em = EMF.get().createEntityManager();
			String jpaql = "SELECT s FROM Student s";
			Query query = em.createQuery(jpaql);
			return query.getResultList().size();
		} catch (Exception ex) {
			ex.printStackTrace();
			throw new AppSystemException(ex);
		} finally {
			em.close();
		}
	}

	@Override
	public void deleteStudent(Key studentKey) {
		EntityManager em = null;
		EntityTransaction transaction = null;
		try {
			em = EMF.get().createEntityManager();
			transaction = em.getTransaction();
			transaction.begin();
			Student student = em.find(Student.class, studentKey);
			if (student != null) {
				em.remove(student);
			}
			transaction.commit();
		} catch (Exception ex) {
			if (transaction != null && transaction.isActive()) {
				transaction.rollback();
			}
			ex.printStackTrace();
			throw new AppSystemException(ex);
		}
	}

	@Override
	public Student findStudentByKey(Key studentKey) {
		EntityManager em = null;
		try {
			em = EMF.get().createEntityManager();
			return em.find(Student.class, studentKey);
		} catch (Exception ex) {
			ex.printStackTrace();
			throw new AppSystemException(ex);
		} finally {
			em.close();
		}
	}

	@Override
	public Student updateStudent(Student student) {
		EntityManager em = null;
		EntityTransaction transaction = null;
		try {
			em = EMF.get().createEntityManager();
			transaction = em.getTransaction();
			transaction.begin();
			Student s = em.find(Student.class, student.getStudentKey());
			if (s != null) {
				s.setStudentId(student.getStudentId());
				s.setStudentName(student.getStudentName());
				s.setStudentEmail(student.getStudentEmail());
				s.setStudentPassword(student.getStudentPassword());
				s.setClassName(student.getClassName());
				s.setCourse(student.getCourse());
				s.setActive(student.isActive());
				s.setRoles(student.getRoles());
				em.merge(s);
				em.flush();
			}
			transaction.commit();
			return s;
		} catch (Exception ex) {
			if (transaction != null && transaction.isActive()) {
				transaction.rollback();
			}
			ex.printStackTrace();
			throw new AppSystemException(ex);
		}
	}

	@SuppressWarnings("unchecked")
	@Override
	public boolean isEmailDuplicatedWithOrtherStudent(String studentEmail, Key studentKey) {
		EntityManager em = null;
		try {
			em = EMF.get().createEntityManager();
			String jpaql = "SELECT s FROM Student s WHERE s.studentKey <> :studentKey AND s.studentEmail = :studentEmail";
			Query query = em.createQuery(jpaql);
			query.setParameter("studentKey", studentKey);
			query.setParameter("studentEmail", studentEmail);
			List<Student> students = (List<Student>) query.getResultList();
			return students != null && !students.isEmpty();
		} catch (Exception ex) {
			ex.printStackTrace();
			throw new AppSystemException(ex);
		}
	}

	@Override
	public int countFoundStudents(SearchStudentValueObject searchObj) {
		return getSearchQuery(searchObj).getResultList().size();
	}
	
	@SuppressWarnings("unchecked")
	@Override
	public List<Student> searchStudent(SearchStudentValueObject searchObj, int page, int pageSize) {
		return (List<Student>) getSearchQuery(searchObj).setFirstResult((page - 1) * pageSize)
				.setMaxResults(pageSize).getResultList();
	}
	
	private Query getSearchQuery(SearchStudentValueObject searchObj) {
		EntityManager em = null;
		try {
			em = EMF.get().createEntityManager();
			CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
			CriteriaQuery<Student> criteriaQuery = criteriaBuilder.createQuery(Student.class);
			Root<Student> root = criteriaQuery.from(Student.class);
			List<Predicate> predicates = new ArrayList<Predicate>();
			
			if (searchObj.getStudentId() != null && searchObj.getStudentId().length() > 0) {
				Expression<String> studentIdExpression = root.get("studentId");
				predicates.add(criteriaBuilder.equal(studentIdExpression, searchObj.getStudentId()));
			}
			
			if (searchObj.getStudentName() != null && searchObj.getStudentName().length() > 0) {
				Expression<String> studentNameExpression = root.get("studentName");
				predicates.add(criteriaBuilder.equal(studentNameExpression, searchObj.getStudentName()));
			}
			
			if (searchObj.getStudentEmail() != null && searchObj.getStudentEmail().length() > 0) {
				Expression<String> studentEmailExpression = root.get("studentEmail");
				predicates.add(criteriaBuilder.equal(studentEmailExpression, searchObj.getStudentEmail()));
			}
			
			if (searchObj.getClassName() != null && searchObj.getClassName().length() > 0) {
				Expression<String> classNameExpression = root.get("className");
				predicates.add(criteriaBuilder.equal(classNameExpression, searchObj.getClassName()));
			}
			
			if (searchObj.getCourse() != null) {
				Expression<Integer> courseExpression = root.get("course");
				predicates.add(criteriaBuilder.equal(courseExpression, searchObj.getCourse()));
			}
			
			if (searchObj.isActive() != null) {
				Expression<Boolean> isActiveExpression = root.get("isActive");
				predicates.add(criteriaBuilder.equal(isActiveExpression, searchObj.isActive()));
			}
			
			if (predicates.isEmpty()) {
				criteriaQuery.select(root);
			} else {
				criteriaQuery.select(root).where(predicates.toArray(new Predicate[predicates.size()]));
			}
			
			TypedQuery<Student> query = em.createQuery(criteriaQuery);
			return query;
		} catch (Exception ex) {
			ex.printStackTrace();
			throw new AppSystemException(ex);
		}
	}

	@Override
	public List<Student> listStudentsInClass(Key classKey, int page, int pageSize) {
		EntityManager em = null;
		try {
			em = EMF.get().createEntityManager();
			Clazz c = em.find(Clazz.class, classKey);
			List<Student> students = new ArrayList<Student>();
			if (c != null) {
				Set<Key> keys = c.getStudentKeys();
				List<Key> studentKeys = new ArrayList<Key>(keys);
				if (studentKeys != null) {
					int from = pageSize * (page - 1) > studentKeys.size() ? 0 : pageSize * (page - 1);
					int to = from + pageSize > studentKeys.size() ? studentKeys.size() : from + pageSize;
					Student s = null;
					for (int i = from; i < to; i++) {
						s = em.find(Student.class, studentKeys.get(i));
						if (s != null) {
							students.add(s);
						}
					}
				}
			}
			return students;
		} catch (Exception ex) {
			ex.printStackTrace();
			throw new AppSystemException(ex);
		} finally {
			em.close();
		}
	}

	@Override
	public int countStudentsInClass(Key classKey) {
		EntityManager em = null;
		try {
			em = EMF.get().createEntityManager();
			Clazz c = em.find(Clazz.class, classKey);
			return c != null ? c.getStudentKeys().size() : 0;
		} catch (Exception ex) {
			ex.printStackTrace();
			throw new AppSystemException(ex);
		}
	}

	@SuppressWarnings("unchecked")
	@Override
	public List<Clazz> listClassesStudentRegistered(String studentId, Integer semester) {
		EntityManager em = null;
		try {
			em = EMF.get().createEntityManager();
			String jpaql = "SELECT c FROM Clazz c WHERE c.semester = :semester";
			Query query = em.createQuery(jpaql);
			query.setParameter("semester", semester);
			List<Clazz> classes = (List<Clazz>) query.getResultList();
			List<Clazz> registeredClasses = new ArrayList<Clazz>();
			if (classes != null && !classes.isEmpty()) {
				jpaql = "SELECT s FROM Student s WHERE s.studentId = :studentId";
				query = em.createQuery(jpaql);
				query.setParameter("studentId", studentId);
				List<Student> students = (List<Student>) query.getResultList();
				Student student = (students != null && !students.isEmpty()) ? students.get(0) : null;
				if (student != null) {
					Iterator<Clazz> iterator = classes.iterator();
					Clazz c = null;
					Key studentKey = student.getStudentKey();
					while (iterator.hasNext()) {
						c = iterator.next();
						if (c.getStudentKeys().contains(studentKey)) {
							registeredClasses.add(c);
						}
					}
				}
			}
			
			return registeredClasses;
		} catch (Exception ex) {
			ex.printStackTrace();
			throw new AppSystemException(ex);
		}
	}

	@SuppressWarnings("unchecked")
	@Override
	public boolean checkStudentRegisteredClass(String studentId, Key classKey) {
		EntityManager em = null;
		try {
			em = EMF.get().createEntityManager();
			String jpaql = "SELECT s FROM Student s WHERE s.studentId = :studentId";
			Query query = em.createQuery(jpaql);
			query.setParameter("studentId", studentId);
			List<Student> students = (List<Student>) query.getResultList();
			if (students != null && !students.isEmpty()) {
				Key key = students.get(0).getStudentKey();
				Clazz clazz = em.find(Clazz.class, classKey);
				if (clazz != null) {
					Set<Key> studentKeys = clazz.getStudentKeys();
					return studentKeys.contains(key);
				}
			}
			return false;
		} catch (Exception ex) {
			ex.printStackTrace();
			throw new AppSystemException(ex);
		}
	}
}
